import 'package:bloc/bloc.dart';
import 'package:dartz/dartz.dart';
import 'package:mobile_app.core/auth/application/bloc/bloc.dart';
import 'package:mobile_app.core/core/domain/config/config.dart';
import 'package:mobile_app.core/core/domain/utils/either_extension.dart';
import 'package:mobile_app.core/core/domain/utils/with_previous_stream.dart';
import 'package:mobile_app.core/purchases/application/models/purchase_obtainment_statuses.dart';
import 'package:mobile_app.core/purchases/application/models/purchase_restoration_failures.dart';
import 'package:mobile_app.core/purchases/application/models/purchase_restoration_statuses.dart';
import 'package:mobile_app.core/purchases/domain/use_cases/obtain_purchased_items/use_case.dart';
import 'package:mobile_app.core/purchases/domain/use_cases/restore_purchased_items/use_case.dart';
import 'package:mobile_app.core/user_info/application/bloc/bloc.dart';

part 'bloc_state.dart';

class PurchaseBloc extends Cubit<PurchaseBlocState?> {
  PurchaseBloc({
    required AuthBloc authBloc,
    required UserInfoBloc userInfoBloc,
    required ObtainPurchasedItems obtainPurchasedItems,
    required RestorePurchasedItems restorePurchasedItems,
  })  : _authBloc = authBloc,
        _userInfoBloc = userInfoBloc,
        _obtainPurchasedItems = obtainPurchasedItems,
        _restorePurchasedItems = restorePurchasedItems,
        super(_getInitialState()) {
    stream = super.stream.cast();
    streamWithPrevious = stream.withPrevious;
  }

  final AuthBloc _authBloc;
  final UserInfoBloc _userInfoBloc;

  final ObtainPurchasedItems _obtainPurchasedItems;
  final RestorePurchasedItems _restorePurchasedItems;

  @override
  late final Stream<PurchaseBlocState> stream;

  late final Stream<(PurchaseBlocState, PurchaseBlocState?)> streamWithPrevious;

  static PurchaseBlocState? _getInitialState() {
    if (!Config().purchasesEnabled) {
      return null;
    }

    return const PurchaseBlocState(
      purchaseRestorationStatus: InitialPurchaseRestorationStatus(),
      purchaseObtainmentStatus: PurchaseObtainmentStatus.initial,
    );
  }

  void obtainPurchases() async {
    if (!Config().isPhysicalDevice || !Config().purchasesEnabled) {
      return;
    }

    if (state == null) {
      return;
    }

    if (state!.purchaseObtainmentStatus == PurchaseObtainmentStatus.inProgress) {
      return;
    }

    emit(state!.copyWith(
      purchaseObtainmentStatus: () => PurchaseObtainmentStatus.inProgress,
    ));

    final purchasedItemObtainmentFailureOrResult = await _obtainPurchasesTransactionally();

    if (purchasedItemObtainmentFailureOrResult.isLeft()) {
      final purchasedItemObtainmentFailure = purchasedItemObtainmentFailureOrResult.asLeft();

      switch (purchasedItemObtainmentFailure) {
        case ServerPurchasedItemObtainmentFailure():
          emit(state!.copyWith(
            purchaseObtainmentStatus: () => PurchaseObtainmentStatus.failed,
          ));

        case NetworkPurchasedItemObtainmentFailure():
          emit(state!.copyWith(
            purchaseObtainmentStatus: () => PurchaseObtainmentStatus.failed,
          ));

        case UnauthorizedPurchasedItemObtainmentFailure():
          emit(state!.copyWith(
            purchaseObtainmentStatus: () => PurchaseObtainmentStatus.failed,
          ));

          _authBloc.setAuthenticationData(
            user: () => purchasedItemObtainmentFailure.user,
            accessCredentials: () => purchasedItemObtainmentFailure.accessCredentials,
          );
      }

      return;
    }

    final purchasedItemObtainmentResult = purchasedItemObtainmentFailureOrResult.asRight();

    _userInfoBloc.updatePremiumAccessData(
      premiumAccessInfo: purchasedItemObtainmentResult.premiumAccessInfo,
      premiumAccessStatus: purchasedItemObtainmentResult.premiumAccessStatus,
    );

    emit(state!.copyWith(
      purchaseObtainmentStatus: () => PurchaseObtainmentStatus.completed,
    ));
  }

  void restorePurchases() async {
    if (!Config().isPhysicalDevice || !Config().purchasesEnabled) {
      return;
    }

    if (state == null) {
      return;
    }

    if (state!.purchaseRestorationStatus is PurchaseRestorationInProgress) {
      return;
    }

    emit(state!.copyWith(
      purchaseRestorationStatus: () => const PurchaseRestorationInProgress(),
    ));

    final purchasedItemRestorationFailureOrResult = await _restorePurchasesTransactionally();

    if (purchasedItemRestorationFailureOrResult.isLeft()) {
      final purchasedItemRestorationFailure = purchasedItemRestorationFailureOrResult.asLeft();

      final PurchaseRestorationFailure purchaseRestorationFailure;

      switch (purchasedItemRestorationFailure) {
        case ServerPurchasedItemRestorationFailure():
          purchaseRestorationFailure = const ServerPurchaseRestorationFailure();
        case NetworkPurchasedItemRestorationFailure():
          purchaseRestorationFailure = const NetworkPurchaseRestorationFailure();
        case UnauthorizedPurchasedItemRestorationFailure():
          purchaseRestorationFailure = const UnauthorizedPurchaseRestorationFailure();

          _authBloc.setAuthenticationData(
            user: () => purchasedItemRestorationFailure.user,
            accessCredentials: () => purchasedItemRestorationFailure.accessCredentials,
          );
      }

      final purchaseRestorationStatus = PurchaseRestorationFailed(
        failure: purchaseRestorationFailure,
      );

      emit(state!.copyWith(
        purchaseRestorationStatus: () => purchaseRestorationStatus,
      ));

      return;
    }

    final purchasedItemRestorationResult = purchasedItemRestorationFailureOrResult.asRight();

    _userInfoBloc.updatePremiumAccessData(
      premiumAccessInfo: purchasedItemRestorationResult.premiumAccessInfo,
      premiumAccessStatus: purchasedItemRestorationResult.premiumAccessStatus,
    );

    final purchaseRestorationStatus = PurchaseRestorationCompleted(
      premiumAccessActive: purchasedItemRestorationResult.premiumAccessStatus.hasFullAccess,
    );

    emit(state!.copyWith(
      purchaseRestorationStatus: () => purchaseRestorationStatus,
    ));
  }

  Future<Either<PurchasedItemObtainmentFailure, PurchasedItemObtainmentResult>>
      _obtainPurchasesTransactionally() async {
    final deviceId = _userInfoBloc.state!.deviceId;
    final introFinished = _userInfoBloc.state!.introFinished;
    final demoExpirationTime = _userInfoBloc.state!.premiumAccessInfo.demoExpirationTime;

    final purchasedItemObtainmentFailureOrResult = await _obtainPurchasedItems(
      deviceId: deviceId,
      introFinished: introFinished,
      demoExpirationTime: demoExpirationTime,
    );

    if (deviceId != _userInfoBloc.state!.deviceId ||
        introFinished != _userInfoBloc.state!.introFinished ||
        demoExpirationTime != _userInfoBloc.state!.premiumAccessInfo.demoExpirationTime) {
      return _obtainPurchasesTransactionally();
    }

    return purchasedItemObtainmentFailureOrResult;
  }

  Future<Either<PurchasedItemRestorationFailure, PurchasedItemRestorationResult>>
      _restorePurchasesTransactionally() async {
    final deviceId = _userInfoBloc.state!.deviceId;
    final introFinished = _userInfoBloc.state!.introFinished;
    final demoExpirationTime = _userInfoBloc.state!.premiumAccessInfo.demoExpirationTime;

    final purchasedItemRestorationFailureOrResult = await _restorePurchasedItems(
      deviceId: deviceId,
      introFinished: introFinished,
      demoExpirationTime: demoExpirationTime,
    );

    if (deviceId != _userInfoBloc.state!.deviceId ||
        introFinished != _userInfoBloc.state!.introFinished ||
        demoExpirationTime != _userInfoBloc.state!.premiumAccessInfo.demoExpirationTime) {
      return _restorePurchasesTransactionally();
    }

    return purchasedItemRestorationFailureOrResult;
  }
}
